﻿using System;
using System.Json;
using ToodledoAPI.DataTypes;
using ToodledoAPI.Exceptions;
using IS = System.IO.IsolatedStorage;

namespace ToodledoAPI
{
    public static class account
    {
        #region Get Key
        /// <summary>
        /// Return the Key for a given email and password.
        /// This method will cach the key and return from cach if less than 3.5 hours old.
        /// This is to prevent problems with the hourly limit on the number of key requests for Toodledo.
        /// </summary>
        /// <param name="email">The users's email</param>
        /// <param name="password">The user's password</param>
        /// <param name="Version">Optional version number for logging purposes</param>
        /// <param name="Device">Optional device name for logging purposes</param>
        /// <param name="OS">Optional Operating System version for logging purposes</param>
        /// <param name="returnAction">The function to call with the returned key.</param>
        public static void GetKey(string email, string password, Action<Key> returnAction)
        {
            Key k;
            if (TryLoadKey(email, password, out k))
            {
                returnAction(k);
                return;
            }
            lookup(email, password, (userid) =>
            {
                token(userid, password, (key) =>
                    {
                        SaveKey(key, email, password);
                        returnAction(key);
                    });
            });
        }

        static void token(UserID userid, string Password, Action<Key> returnAction)
        {
            string Method;
            if (ApplicationConfiguration.DeviceSpecification != null)
            {
                Method = "account/token.php?" +
                    String.Format("userid={0};appid={1};vers={2};device={3};os={4};sig={5}"
                    , userid, ApplicationConfiguration.AppID, ApplicationConfiguration.DeviceSpecification.Version, ApplicationConfiguration.DeviceSpecification.Device, ApplicationConfiguration.DeviceSpecification.OS, MD5(userid + ApplicationConfiguration.AppToken));
            }
            else
            {
                Method = "account/token.php?" +
                    String.Format("userid={0};appid={1};sig={2}" , userid, ApplicationConfiguration.AppID, MD5(userid + ApplicationConfiguration.AppToken));
            }

            ApplicationConfiguration.CallWebService(Method, (s) => returnAction(new Key(new Token(s), Password)));
        }
        static void lookup(string email, string pass, Action<UserID> returnAction)
        {
            string Method = "account/lookup.php?" +
                String.Format("appid={0};sig={1};email={2};pass={3}"
                , ApplicationConfiguration.AppID, MD5(email + ApplicationConfiguration.AppToken), email, pass);
            ApplicationConfiguration.CallWebService(Method, (s) => returnAction(new UserID(s)));
        }

        static void SaveKey(Key key, string Username, string Password)
        {
            if (IS.IsolatedStorageFile.IsEnabled)
            {
                IS.IsolatedStorageSettings Settings = IS.IsolatedStorageSettings.ApplicationSettings;
                Settings[MD5(Username + Password) + "Date"] = DateTime.UtcNow;
                Settings[MD5(Username + Password)] = key.ToString();
                Settings.Save();
            }
        }
        static bool TryLoadKey(string Username, string Password, out Key k)
        {
            if (IS.IsolatedStorageFile.IsEnabled)
            {
                IS.IsolatedStorageSettings Settings = IS.IsolatedStorageSettings.ApplicationSettings;
                if (Settings.Contains(MD5(Username + Password) + "Date") && Settings.Contains(MD5(Username + Password)))
                {
                    DateTime SavedKeyDate = (DateTime)Settings[MD5(Username + Password) + "Date"];
                    if (DateTime.UtcNow.Subtract(SavedKeyDate).TotalHours < 3.5)
                    {
                        k = new Key(Settings[MD5(Username + Password)].ToString());
                        return true;
                    }
                }
            }
            k = null;
            return false;
        }

        static string MD5(string s)
        {
            return s.MD5();
        }

        internal class Token : ValueDefinition<Token, string>
        {
            internal Token(string value)
            {
                if (value.IsToodledoException())
                {
                    throw new accountException(value);
                }
                JsonObject jsonToken = (JsonObject)JsonObject.Parse(value);
                Value = jsonToken["token"];
            }

            public override string ToString()
            {
                return Value;
            }
        }
        class UserID : ValueDefinition<UserID, string>
        {
            internal UserID(string value)
            {
                if (value.IsToodledoException())
                {
                    throw new accountException(value);
                }
                JsonObject jsonUserID = (JsonObject)JsonObject.Parse(value);
                Value = jsonUserID["userid"];
            }

            public override string ToString()
            {
                return Value.ToString();
            }
        }
        #endregion

        /// <summary>
        /// Get the current account info.
        /// This includes details about the user, and last edit dates for everything.
        /// </summary>
        /// <param name="key">The user's key</param>
        /// <param name="returnAction">The return method</param>
        public static void getAccountInfo(Key key, Action<AccountInfo> returnAction)
        {
            string Method = "account/get.php?key=" + key.ToString();

            ApplicationConfiguration.CallWebService(Method, (JsonReturn) =>
                {
                    returnAction(new AccountInfo(JsonReturn));
                });
        }

    }

    namespace DataTypes
    {
        public class Key : ValueDefinition<Key, string>
        {
            internal Key(string SavedKey)
            {
                Value = SavedKey;
            }
            internal Key(account.Token token, string password)
            {
                Value = MD5(MD5(password) + ApplicationConfiguration.AppToken + token.ToString());
            }
            static string MD5(string s)
            {
                return s.MD5();
            }

            public override string ToString()
            {
                return Value;
            }
        }

        public abstract class ValueDefinition<T, S> : IEquatable<T>
            where T : ValueDefinition<T, S>
            where S : IEquatable<S>
        {

            protected S Value;

            public override bool Equals(object obj)
            {
                if (obj == null || !(obj is T))
                {
                    return false;
                }
                return Equals((T)obj);
            }

            public override int GetHashCode()
            {
                return Value.GetHashCode();
            }

            public bool Equals(T other)
            {
                return Value.Equals(other.Value);
            }
            public static bool operator ==(ValueDefinition<T, S> Item1, ValueDefinition<T, S> Item2)
            {
                if (((object)Item1) == null)
                    return (((object)Item2) == null);
                return Item1.Equals(Item2);
            }
            public static bool operator !=(ValueDefinition<T, S> Item1, ValueDefinition<T, S> Item2)
            {
                return !(Item1 == Item2);
            }
        }

        public class AccountInfo
        {
            public AccountInfo(string s)
            {
                JsonObject AccountObject = (JsonObject)JsonObject.Parse(s);
                userid = AccountObject["userid"];
                alias = AccountObject["alias"];
                pro = AccountObject["pro"] == "1";
                dateformat = int.Parse(AccountObject["dateformat"]);
                timezone = int.Parse(AccountObject["timezone"]);
                hidemonths = int.Parse(AccountObject["hidemonths"]);
                hotlistpriority = int.Parse(AccountObject["hotlistpriority"]);
                hotlistduedate = int.Parse(AccountObject["hotlistduedate"]);
                hotliststar = AccountObject["hotliststar"] == "1";
                hotliststatus = AccountObject["hotliststatus"] == "1";
                showtabnums = AccountObject["showtabnums"] == "1";

                lastedit_task = UnixTime.Parse(AccountObject["lastedit_task"]);
                lastdelete_task = UnixTime.Parse(AccountObject["lastdelete_task"]);
                lastedit_folder = UnixTime.Parse(AccountObject["lastedit_folder"]);
                lastedit_context = UnixTime.Parse(AccountObject["lastedit_context"]);
                lastedit_goal = UnixTime.Parse(AccountObject["lastedit_goal"]);
                lastedit_location = UnixTime.Parse(AccountObject["lastedit_location"]);
                lastedit_notebook = UnixTime.Parse(AccountObject["lastedit_notebook"]);
                lastdelete_notebook = UnixTime.Parse(AccountObject["lastdelete_notebook"]);
            }

            public string userid { get; set; }
            public string alias { get; set; }

            /// <summary>
            /// You need to know if a user is pro if you wish to use subtasks.
            /// </summary>
            public bool pro { get; set; }
            /// <summary>
            /// User's preffered way or representing dates
            /// 0=M D, Y
            /// 1=M/D/Y
            /// 2=D/M/Y
            /// 3=Y-M-D
            /// </summary>
            public int dateformat { get; set; }
            /// <summary>
            /// The number of half hours that the user's timezone is offset form the server's timezone.
            /// </summary>
            public int timezone { get; set; }
            /// <summary>
            /// If the task is due this many months into the future, the user wants them hidden.
            /// </summary>
            public int hidemonths { get; set; }
            /// <summary>
            /// The priority above which tasks should appear on the hotlist
            /// </summary>
            public int hotlistpriority { get; set; }
            /// <summary>
            /// The due date lead-time by which tasks should will appear on the hotlist.
            /// </summary>
            public int hotlistduedate { get; set; }
            /// <summary>
            /// true if the star contributes to the hotlist.
            /// </summary>
            public bool hotliststar { get; set; }
            /// <summary>
            /// true if the status contributes to the hotlist.
            /// </summary>
            public bool hotliststatus { get; set; }
            /// <summary>
            /// The user's preference for viewing counters showing the number of tasks in each section.
            /// </summary>
            public bool showtabnums { get; set; }

            /// <summary>
            /// A timestamp that indicates the last time that any task was added or edited on this account.
            /// You can quickly check this field to determine if you need to download updates.
            /// </summary>
            public DateTime lastedit_task { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that any task was deleted from this account. 
            /// You can quickly check this field to determine if you need to identify and remove tasks from your application
            /// </summary>
            public DateTime lastdelete_task { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that a folder was added, edited or deleted. 
            /// You can quickly check this field to determine if you need to refresh your cached folder list.
            /// </summary>
            public DateTime lastedit_folder { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that a context was added, edited or deleted. 
            /// You can quickly check this field to determine if you need to refresh your cached context list.
            /// </summary>
            public DateTime lastedit_context { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that a goal was added, edited or deleted. 
            /// You can quickly check this field to determine if you need to refresh your cached goal list.
            /// </summary>
            public DateTime lastedit_goal { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that a location was added, edited or deleted. 
            /// You can quickly check this field to determine if you need to refresh your cached location list.
            /// </summary>
            public DateTime lastedit_location { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that any notebook was added or edited on this account. 
            /// You can quickly check this field to determine if you need to download updates.
            /// </summary>
            public DateTime lastedit_notebook { get; set; }
            /// <summary>
            /// A timestamp that indicates the last time that any notebook was deleted from this account. 
            /// You can quickly check this field to determine if you need to identify and remove notebooks from your application
            /// </summary>
            public DateTime lastdelete_notebook { get; set; }
        }
    }

}
